package org.rainwalk.library.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.rainwalk.library.api.exception.BizException;
import org.rainwalk.library.api.model.ResponseCode;
import org.rainwalk.library.entity.BrowseLog;
import org.rainwalk.library.entity.Storage;
import org.rainwalk.library.mapper.StorageMapper;
import org.rainwalk.library.model.dto.DirectoryAddDTO;
import org.rainwalk.library.model.dto.FileAddDTO;
import org.rainwalk.library.model.enums.FileType;
import org.rainwalk.library.model.vo.DirectoryVO;
import org.rainwalk.library.model.vo.FileOpenVO;
import org.rainwalk.library.model.vo.StorageVO;
import org.rainwalk.library.service.IBrowseLogService;
import org.rainwalk.library.service.IFileService;
import org.rainwalk.library.service.ILibraryService;
import org.rainwalk.library.service.IStorageService;
import org.rainwalk.library.util.RequestUtil;
import org.rainwalk.library.util.TimeUtil;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * <p>
 * 存储项目 服务实现类
 * </p>
 *
 * @author 趁雨行
 * @since 2021-07-06
 */
@Slf4j
@Service
@RequiredArgsConstructor(onConstructor_={@Autowired})
public class StorageServiceImpl extends ServiceImpl<StorageMapper, Storage> implements IStorageService, InitializingBean {
    /**
     * 顶级目录id
     */
    private static final long TOP_DIR_ID = 0L;

    @Value("${file.dir}")
    private String fileStoreDirectory;

    private final ILibraryService libraryService;

    private final IBrowseLogService browseLogService;

    private final List<IFileService> fileServices;

    @Autowired
    private IStorageService storageService;

    @Override
    public void afterPropertiesSet() {
        if (!fileStoreDirectory.endsWith(File.pathSeparator)) {
            fileStoreDirectory = fileStoreDirectory + File.separator;
        }
    }

    @Override
    public StorageVO addDirectory(DirectoryAddDTO directoryAddDTO) {
        log.info("新增目录-----服务层-----入参：{}", directoryAddDTO);
        if (StrUtil.isBlankIfStr(directoryAddDTO.getPid())) {
            directoryAddDTO.setPid(TOP_DIR_ID);
        }
        Storage storage = directoryAddDto2storage(directoryAddDTO);
        log.info("新增目录-----服务层-----入库：{}", storage);
        if (baseMapper.insertWhenPidExists(storage) == 0) {
            log.warn("新增目录-----服务层-----新增目录失败：{}", directoryAddDTO);
            throw new BizException(ResponseCode.PARAMS_VERITY_FAIL, "新增目录失败");
        }
        StorageVO storageVO = storage2storageVO(storage);
        log.info("新增目录-----服务层-----出参：{}", storageVO);
        return storageVO;
    }

    @Override
    public StorageVO addFile(FileAddDTO fileAddDTO, FileType fileType) {
        log.info("上传文件-----服务层-----入参：{}", fileAddDTO);
        Optional<IFileService> fileServiceOptional = getFileService(fileType);
        if (!fileServiceOptional.isPresent()) {
            log.warn("上传文件-----服务层-----没有找到支持该文件类型的文件服务");
            throw new BizException(ResponseCode.PARAMS_VERITY_FAIL, "文件类型不支持");
        }
        IFileService fileService = fileServiceOptional.get();
        if (StrUtil.isBlankIfStr(fileAddDTO.getPid())) {
            fileAddDTO.setPid(TOP_DIR_ID);
        }
        log.info("上传文件-----服务层-----存储文件到本地：{}", fileAddDTO);
        File file;
        try {
            file = fileService.saveToDisk(fileAddDTO.getFile());
        } catch (IOException e) {
            log.info("上传文件-----服务层-----存储文件到本地失败", e);
            throw new BizException(ResponseCode.SERVER_ERROR);
        }
        Storage storage = fileAddDto2storage(fileAddDTO, file, fileType);
        log.info("上传文件-----服务层-----入库：{}", storage);
        if (baseMapper.insertWhenPidExists(storage) == 0) {
            log.warn("上传文件-----服务层-----新增文件失败：{}", storage);
            throw new BizException(ResponseCode.PARAMS_VERITY_FAIL, "新增文件失败");
        }
        StorageVO storageVO = storage2storageVO(storage);
        //异步调用一下es写入
        libraryService.write(storage);
        log.info("上传文件-----服务层-----出参：{}", storageVO);
        return storageVO;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(Long id) {
        log.info("删除文件-----服务层-----入参：{}", id);
        Storage storage = baseMapper.selectById(id);
        if (baseMapper.deleteById(id) == 0) {
            log.info("删除文件-----服务层-----删除条数为0");
            throw new BizException(ResponseCode.PARAMS_VERITY_FAIL, "删除失败");
        }
        if (storage == null || !storage.getIsDirectory()) {
            return;
        }
        //目录需要删除其下级文件
        log.info("删除文件-----服务层-----需要删除下级文件: pid={}", id);
        LambdaQueryWrapper<Storage> query = new LambdaQueryWrapper<>();
        query.eq(Storage::getPid, id);
        List<Storage> children = baseMapper.selectList(query);
        for (Storage child : children) {
            log.info("删除文件-----服务层-----删除下级文件: {}", child);
            storageService.delete(child.getId());
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(List<Long> ids) {
        for (Long id : ids) {
            storageService.delete(id);
        }
    }

    @Override
    public List<Storage> deletedFileList() {
        return baseMapper.deletedFileList();
    }

    @Override
    public void edit(Long id, String title) {
        log.info("编辑文件-----服务层-----入参：id={}, title={}", id, title);
        if (baseMapper.updateTitle(id, title) == 0) {
            log.info("编辑文件-----服务层-----编辑条数为0");
            throw new BizException(ResponseCode.PARAMS_VERITY_FAIL, "文件已存在");
        }
    }

    @Override
    public List<StorageVO> findByTitle(String title) {
        LambdaQueryWrapper<Storage> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.like(Storage::getTitle, title)
                .orderByDesc(Storage::getIsDirectory)
                .orderByAsc(Storage::getTitle);
        List<Storage> storages = baseMapper.selectList(queryWrapper);
        return storages.stream()
                .map(this::storage2storageVO)
                .collect(Collectors.toList());
    }

    @Override
    public FileOpenVO getFile(Long id) {
        Storage storage = baseMapper.selectById(id);
        if (storage == null || storage.getIsDirectory()) {
            throw new BizException(ResponseCode.PARAMS_VERITY_FAIL, "文件不存在");
        }
        Optional<IFileService> fileServiceOptional = getFileService(FileType.getByValue(storage.getFileType()));
        if (!fileServiceOptional.isPresent()) {
            throw new BizException(ResponseCode.SERVER_ERROR, "文件类型不存在");
        }
        IFileService fileService = fileServiceOptional.get();
        Optional<File> fileOptional = fileService.fetchFile(storage.getRelativePath());
        if (! fileOptional.isPresent()) {
            throw new BizException(ResponseCode.PARAMS_VERITY_FAIL, "文件不存在");
        }
        File file = fileOptional.get();
        //插入浏览记录
        HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
        String ip = RequestUtil.getRemoteAddr(request);
        BrowseLog browseLog = new BrowseLog();
        browseLog.setIp(ip);
        browseLog.setLogTime(TimeUtil.getNow());
        browseLog.setStorageId(id);
        browseLogService.save(browseLog);

        FileOpenVO fileOpenVO = new FileOpenVO();
        fileOpenVO.setTitle(storage.getTitle());
        fileOpenVO.setType(storage.getFileType());
        fileOpenVO.setFile(file);
        return fileOpenVO;
    }

    @Override
    public DirectoryVO getRoute(Long id) {
        DirectoryVO root = new DirectoryVO();
        root.setId(0L);
        root.setTitle("root");
        if (id == 0L) {
            return root;
        }

        DirectoryVO child;

        Storage storage = baseMapper.selectById(id);
        DirectoryVO directoryVO = new DirectoryVO();
        directoryVO.setId(id);
        directoryVO.setTitle("root");
        child = storage2DirectoryVO(storage, null);
        while (storage.getPid() != 0) {
            storage = baseMapper.selectById(storage.getPid());
            child = storage2DirectoryVO(storage, child);
        }

        root.setChild(child);
        return root;
    }

    @Override
    public void move(Long id, Long pid) {
        log.info("移动文件-----服务层-----入参：id={}, pid={}", id, pid);
        Storage storage = baseMapper.selectById(id);
        if (storage == null) {
            log.info("移动文件-----服务层-----文件不存在：id={}", id);
            throw new BizException(ResponseCode.PARAMS_VERITY_FAIL, "文件不存在");
        }
        storage.setPid(pid);
        if (baseMapper.updatePid(storage) == 0) {
            log.info("移动文件-----服务层-----移动条数为0");
            throw new BizException(ResponseCode.PARAMS_VERITY_FAIL, "移动失败");
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void move(List<Long> ids, Long pid) {
        log.info("批量移动文件-----服务层-----入参：ids={}, pid={}", ids, pid);
        for (Long id : ids) {
            storageService.move(id, pid);
        }
    }

    @Override
    public List<StorageVO> toDirectory(Long id, boolean onlyDirectory) {
        LambdaQueryWrapper<Storage> queryWrapper = new LambdaQueryWrapper<>();
        if (onlyDirectory) {
            queryWrapper.eq(Storage::getIsDirectory, true);
        }
        queryWrapper.eq(Storage::getPid, id)
                .orderByDesc(Storage::getIsDirectory)
                .orderByAsc(Storage::getTitle);
        List<Storage> storages = baseMapper.selectList(queryWrapper);
        return storages.stream()
                .map(this::storage2storageVO)
                .collect(Collectors.toList());
    }

    /**
     * 根据文件类型获取文件服务Bean
     *
     * @param fileType 文件类型
     * @return 文件服务
     */
    private Optional<IFileService> getFileService(FileType fileType) {
        return fileServices.stream()
                .filter(fileService -> fileService.support(fileType).isBool())
                .findFirst();
    }

    private Storage fileAddDto2storage(FileAddDTO fileAddDTO, File file, FileType fileType) {
        Storage storage = new Storage();
        storage.setPid(fileAddDTO.getPid());
        storage.setTitle(fileAddDTO.getTitle());
        storage.setIsDirectory(false);
        storage.setFileType(fileType.getValue());
        storage.setRelativePath(file.getName());
        storage.setEsWrite(false);
        storage.setCreateTime(TimeUtil.getNow());
        return storage;
    }

    private StorageVO storage2storageVO(Storage storage) {
        StorageVO storageVO = new StorageVO();
        storageVO.setId(storage.getId());
        storageVO.setTitle(storage.getTitle());
        storageVO.setDirectoryFlag(storage.getIsDirectory());
        storageVO.setFileType(storage.getFileType());
        return storageVO;
    }

    private Storage directoryAddDto2storage(DirectoryAddDTO directoryAddDTO) {
        Storage storage = new Storage();
        storage.setPid(directoryAddDTO.getPid());
        storage.setTitle(directoryAddDTO.getTitle());
        storage.setIsDirectory(true);
        storage.setCreateTime(TimeUtil.getNow());
        return storage;
    }

    private DirectoryVO storage2DirectoryVO(Storage storage, DirectoryVO child) {
        DirectoryVO vo = new DirectoryVO();
        vo.setId(storage.getId());
        vo.setTitle(storage.getTitle());
        vo.setChild(child);
        return vo;
    }
}
